package com.gosling.cloudspace.config.web;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.gosling.cloudspace.common.constants.CommonCodeEnum;
import com.gosling.cloudspace.common.constants.CommonConstants;
import com.gosling.cloudspace.common.entity.BaseResult;
import com.gosling.cloudspace.common.exception.CommonException;
import com.sprometheus.core.constants.SPrometheusTitleEnum;
import com.sprometheus.core.handler.SPrometheusUtil;
import com.sprometheus.core.pojo.Exp;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;



@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

    @SneakyThrows
    private static String getParams(HttpServletRequest request) {
        HashMap<String, String> params = new HashMap<>(16);
        //先获取get请求中参数
        paramsByGet(request, params);
        //后获取post请求中参数
        paramsByPost(request, params);
        //最后获取reader中参数
        paramsByReader(request, params);
        return JSONObject.toJSONString(params);
    }

    private static void paramsByPost(HttpServletRequest request, HashMap<String, String> params) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            params.put(entry.getKey(), entry.getValue()[0]);
        }
    }

    private static void paramsByReader(HttpServletRequest request, HashMap<String, String> params) {
        if (request instanceof ContentCachingRequestWrapper) {
            String body = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray());
            if (StrUtil.isNotBlank(body)) {
                //不用 toJson 方式，如果接口定义为String的body接收，那么则toJson会失败
                params.put("requestBodyParams", body);
            }
        }
    }

    private static void paramsByGet(HttpServletRequest request, HashMap<String, String> params) {
        String queryString = request.getQueryString();
        if (StrUtil.isNotBlank(queryString)) {
            String[] pairs = queryString.split("&");
            for (String pair : pairs) {
                int idx = pair.indexOf('=');
                if (idx > 0) {
                    String key = pair.substring(0, idx);
                    String value = pair.substring(idx + 1);
                    params.put(key, value);
                } else if (idx == 0 && pair.length() > 1) {
                    params.put("", pair.substring(1));
                } else {
                    params.put("", pair);
                }
            }
        }
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public BaseResult<?> missingServletRequestParameterExceptionHandler(HttpServletRequest request, MissingServletRequestParameterException ex) {
        log.warn("全局异常处理:参数传参异常", ex);
        String message = String.format("请求参数缺失:%s", ex.getParameterName());
        Exp repayReq = new Exp();
        repayReq.setUri(request.getRequestURI());
        repayReq.setMsg(message);
        repayReq.setReqData(getParams(request));
        repayReq.setTitleEnum(SPrometheusTitleEnum.OTHER);
//        PrometheusUtil.insert(repayReq);
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS, message);
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public BaseResult<?> methodArgumentTypeMismatchExceptionHandler(HttpServletRequest request, MethodArgumentTypeMismatchException ex) {
        log.warn("全局异常处理:参数传参类型异常", ex);
        String message = String.format("请求参数类型错误:%s", ex.getMessage());
        Exp repayReq = new Exp();
        repayReq.setUri(request.getRequestURI());
        repayReq.setMsg(message);
        repayReq.setTitleEnum(SPrometheusTitleEnum.OTHER);
        SPrometheusUtil.insert(repayReq);
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS, message);
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResult<?> methodArgumentNotValidExceptionExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
        log.warn("全局异常处理:参数校验不正确", ex);
        FieldError fieldError = ex.getBindingResult().getFieldError();
        assert fieldError != null; // 断言，避免告警
        String message = String.format("请求参数不正确:%s", fieldError.getDefaultMessage());
        Exp repayReq = new Exp();
        repayReq.setUri(request.getRequestURI());
        repayReq.setMsg(message);
        repayReq.setReqData(getParams(request));
        repayReq.setTitleEnum(SPrometheusTitleEnum.OTHER);
        SPrometheusUtil.insert(repayReq);
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS, message);
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public BaseResult<?> bindExceptionHandler(HttpServletRequest request, BindException ex) {
        log.warn("全局异常处理:参数绑定不正确", ex);
        FieldError fieldError = ex.getFieldError();
        assert fieldError != null; // 断言，避免告警
        String message = String.format("请求参数不正确:%s", fieldError.getDefaultMessage());
        Exp repayReq = new Exp();
        repayReq.setUri(request.getRequestURI());
        repayReq.setMsg(message);
        repayReq.setTitleEnum(SPrometheusTitleEnum.OTHER);
        SPrometheusUtil.insert(repayReq);
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS, message);
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public BaseResult<?> constraintViolationExceptionHandler(HttpServletRequest request, ConstraintViolationException ex) {
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        log.warn("全局异常处理:校验不通过产生的异常:{}", constraintViolation.getMessage());
        String message = String.format("请求参数不正确:%s", constraintViolation.getMessage());
        Exp repayReq = new Exp();
        repayReq.setUri(request.getRequestURI());
        repayReq.setMsg(message);
        repayReq.setTitleEnum(SPrometheusTitleEnum.OTHER);
        SPrometheusUtil.insert(repayReq);
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS, message);
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     * <p>
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public BaseResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
        log.warn("全局异常处理:请求方法不正确", ex);
        String message = String.format("请求方法不正确:%s", ex.getMessage());
        Exp repayReq = new Exp();
        repayReq.setUri(request.getRequestURI());
        repayReq.setMsg(message);
        repayReq.setTitleEnum(SPrometheusTitleEnum.OTHER);
        SPrometheusUtil.insert(repayReq);
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS, message);
    }

    /**
     * 传参方式不正确
     * <p>
     * 例如说，A 接口的方法为 @params 方式，结果请求方法为 @body 方式，导致不匹配
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public BaseResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException ex) {
        log.warn("全局异常处理:传参方式不正确", ex);
        String message = String.format("请求方法不正确:%s", ex.getMessage());
        Exp repayReq = new Exp();
        repayReq.setUri(request.getRequestURI());
        repayReq.setMsg(message);
        repayReq.setTitleEnum(SPrometheusTitleEnum.OTHER);
        SPrometheusUtil.insert(repayReq);
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS, message);
    }

    /**
     * 处理业务异常 ServiceException
     * <p>
     * 例如说，商品库存不足，用户手机号已存在。
     */
    @ExceptionHandler(value = CommonException.class)
    public BaseResult<?> serviceExceptionHandler(CommonException ex) {
        log.error("全局异常处理:业务异常抛出:{}", ex.getMessage());
        if (ex.getCode() != null) {
            return BaseResult.failed(ex.getCode(), ex.getMessage());
        }
        return BaseResult.failed(CommonConstants.FRIENDLY_TIPS);
    }

    /**
     * 处理业务异常 NotPermissionException
     * <p>
     * 无接口权限异常抛出
     */
//    @ExceptionHandler(value = NotPermissionException.class)
//    public BaseResult<?> notPermissionExceptionHandler(NotPermissionException ex) {
//        log.error("全局异常处理:无接口权限异常抛出:{}", ex.getMessage());
//        return BaseResult.failed(GlobalErrorCodeConstants.FORBIDDEN);
//    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public BaseResult<?> defaultExceptionHandler(Exception ex) {
        log.error("全局异常处理:Exception处理:", ex);
        // 返回 ERROR CommonResult
        return BaseResult.failed(CommonCodeEnum.FRIENDLY_TIPS);
    }

}
